package ai;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;


import game.BGBoard;

public class AI_Controller {
	AI_Interface ai;
        
	BGBoard START;
	public EvaluationBoard currentboard, tmpboard;
	public LinkedList<EvaluationBoard> possibleBoards = new LinkedList<EvaluationBoard>();
	public Queue<EvaluationBoard> moveQueue = (Queue<EvaluationBoard>) new LinkedList<EvaluationBoard>();



        public AI_Controller(AI_Interface ai){
            this.ai = ai;
        }

	/**
	 * This method takes a board and evaluates what moves is best
	 * @param board
	 * @return an arraylist of the moves that is to be applied
	 */
	public ArrayList<byte[]> makemoves(BGBoard board){
		/*
		 * General strategy:
		 * 
		 * 1: Put initial board state in queue
		 * 2: While queue is non-empty. Take out first board in queue
		 * 3: IF board has possible moves to do then
		 * 		a: Apply the first possible move
		 * 		b: If there are no more moves to do after the first -> Put in "possibleBoards" List
		 * 			Else. Board has more moves to apply -> Put in queue
		 * 		c: go to (3) and apply the next possible move
		 * 4: Queue is empty we have fully explored the possible states
		 */
		
		START = (BGBoard)board.clone();
		possibleBoards = new LinkedList<EvaluationBoard>();
		moveQueue = (Queue<EvaluationBoard>) new LinkedList<EvaluationBoard>();

//1: Put initial board state in queue
		moveQueue.add(new EvaluationBoard((BGBoard)START.clone()));
		int count = 0;
		
//2: While queue is non-empty. Take out first board in queue
		while(moveQueue.peek() != null){
			currentboard = moveQueue.remove(); 
			ArrayList<byte[]> moves = currentboard.board.getMoves();
			Iterator<byte[]> moveIterator = moves.iterator();
			byte[] move;
			
//3: IF board has possible moves to do then
			while(moveIterator.hasNext()){ //explore fully
				move = moveIterator.next();
				
				
//a: Apply the first possible move
				//quick'n'dirty cloning of EvaluationBoard
				tmpboard = new EvaluationBoard((BGBoard)currentboard.board.clone());
				tmpboard.setMoves(currentboard.getMoves());
				tmpboard.board.applyMove(move[0], move[1]);
				tmpboard.addMove(move[0], move[1]);
				tmpboard.board.GetPossiblePositions(); //update
				//tmpboard.board.showPossiblePositions();
				
//b: If there are no more moves to do after the first -> Put in "possibleBoards" List
				if( tmpboard.board.getMoves().isEmpty()){ //fully moved. Save as finished state

					possibleBoards.add(tmpboard);
					//System.out.println("\nAdding to boards [" + count + "]\n");
					//System.out.println("Moves: " + tmpboard.moves.size() + "\n");
					//tmpboard.board.printBoardArray();
					count++;}
//b: Else. Board has more moves to apply -> Put in queue		
				else
					tmpboard.board.GetPossiblePositions();
					moveQueue.add(tmpboard);
					//more moves to apply, throw it into queue
				
			}//c: go to (3) and apply the next possible move	
		 }
//4: Queue is empty we have fully explored the possible states
		
		//ok, great, we have all the possible boards. Now we want to find the board with the highest score
		//TODO: maybe remove copies before evaluation to increase speed
		double highscore = -999999999;
		double tmpscore;
		EvaluationBoard winner = new EvaluationBoard();
		EvaluationBoard tmp;
		Iterator<EvaluationBoard> it = possibleBoards.iterator();
		byte [] tmpBoard;
		while(it.hasNext()){
			tmp = it.next();
			if(tmp.board.getWhoMove()){ //if ai plays white, we need to switch representation of the board
				tmpBoard = switchColor(tmp.board.getBoard());
			}else{
				tmpBoard = tmp.board.getBoard();
			}
			
			tmpscore = (ai.evaluate(tmpBoard, tmp.board.isRace()));
			//System.out.println("comparing " + tmpscore + "with " + highscore);
			if(tmpscore > highscore){
				highscore = tmpscore;
				winner = tmp;		
			}
		}
		//System.out.print(ai.whatsMahName() + ": highscore: ");
		//System.out.println(highscore);
		
		//great, now we have a winner. What was teh moves?
		//System.out.println(winner.toString());
		
		return winner.getMoves();
	}
	/**
	 * Switches the "color" of the pieces on the board
	 * @param tmp
	 * @return new board (array is allocated new memory space)
	 */
	private byte[] switchColor(byte[] tmp){
		byte[] newBoard = new byte[28];
		for(int i = 0; i<26; i++){
			newBoard[i] = (byte)(-1*tmp[25-i]);
		}
		newBoard[26] = (byte)(-1*tmp[27]);	
		newBoard[27] = (byte)(-1*tmp[26]);
		return newBoard;
	}
	
	/**
	 * This helper class packages a board with its applied moves and a score for easy sorting
	 * @author StrongBad
	 *
	 */
	protected class EvaluationBoard{
		public ArrayList<byte[]> moves = new ArrayList<byte[]>() ; //the moves [from,to]
		public BGBoard board; //the board 
		public float score; //calculated score for this move
		
		public EvaluationBoard(){
		}
		public EvaluationBoard(BGBoard board){
			this.board = board;
		}
		public boolean isEqual(EvaluationBoard e){
			boolean equal = true;
			int i = 0;
			while(equal){
				equal = board.getBoard()[i] == e.board.getBoard()[i];
			}
			
			return equal;
		}
		public BGBoard getBoard(){
			return board;
		}
		public void addMove(byte from, byte to){
			moves.add(new byte[]{from, to});
		}
		public void setMoves(ArrayList<byte[]> moves){
			this.moves = (ArrayList<byte[]>)moves.clone();
		}
		public ArrayList<byte[]> getMoves(){
			return moves;
		}
		public void setScore(float score){
			this.score = score;
		}
		public float getScore(){
			return score;
		}
		public String toString(){
			String tmp = new String("Moves: ");	
			for(int i = 0; i < moves.size();i ++ ){
				tmp += "[" + moves.get(i)[0] + "," + moves.get(i)[1] + "] ";
			}
			return tmp;
		}
		
		
		
	}

		public String whatsUrName(){
			return ai.whatsMahName();
		}

        /*
         * Ann get
         */
        public ANN getANN(){
            return (ANN)ai;
        }

        /*
         * Save ann
         */
        public void write(String fileName){
			FileOutputStream fos = null;
			ObjectOutputStream out = null;
			// for deleting

			try
			{
				fos = new FileOutputStream (fileName);
				out = new ObjectOutputStream(fos);
				out.writeObject(ai);
				out.close();
			}
			catch(IOException ex)
			{
			  ex.printStackTrace();
			}
		}

        /*
         * Load ANN
         */
        public static ANN load(String fileName){
                ANN ann = null;
            FileInputStream fIn = null;
            ObjectInputStream in = null;
            try
            {
                fIn =new  FileInputStream(fileName);
                in = new ObjectInputStream(fIn);
                ann = (ANN)in.readObject();
                in.close();
            }
            catch(IOException ex)
            {
              ex.printStackTrace();
            }
            catch(ClassNotFoundException ex)
            {
              ex.printStackTrace();
            }
            return ann;
        }
}
